﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using Meraz.Core.Data;
using Meraz.Core.Factories;
using Meraz.Core.Model;
using Meraz.Core.Repositories;
using System.Web;
using System.Configuration.Provider;
using Meraz.Core.Comparers;

namespace Meraz.Core.Providers
{
    public interface ICustomMembershipProvider
    {
        #region Properties
        bool EnablePasswordReset { get; }
        bool EnablePasswordRetrieval { get; }
        string ApplicationName { get; set; }
        int MaxInvalidPasswordAttempts { get; }
        Meraz.Core.Repositories.IMemberRepository MemberRepository { get; set; }
        Meraz.Core.Repositories.IMemberRoleRepository MemberRoleRepository { get; set; }
        int MinRequiredNonAlphanumericCharacters { get; }
        int MinRequiredPasswordLength { get; }
        int PasswordAttemptWindow { get; }
        System.Web.Security.MembershipPasswordFormat PasswordFormat { get; }
        string PasswordStrengthRegularExpression { get; }
        bool RequiresQuestionAndAnswer { get; }
        bool RequiresUniqueEmail { get; }
        #endregion
        #region Methods
        void Initialize(string name, System.Collections.Specialized.NameValueCollection config);
        bool ChangePassword(string username, string oldPassword, string newPassword);
        bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
        System.Web.Security.MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out System.Web.Security.MembershipCreateStatus status);
        bool DeleteUser(string username, bool deleteAllRelatedData);
        System.Web.Security.MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords);
        System.Web.Security.MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords);
        System.Web.Security.MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords);
        int GetNumberOfUsersOnline();
        string GetPassword(string username, string answer);
        System.Web.Security.MembershipUser GetUser(object providerUserKey, bool userIsOnline);
        System.Web.Security.MembershipUser GetUser(string username, bool userIsOnline);
        string GetUserNameByEmail(string email);
        string ResetPassword(string username, string answer);
        bool UnlockUser(string userName);
        void UpdateUser(System.Web.Security.MembershipUser user);
        bool ValidateUser(string username, string password); 
        #endregion
    }
    public class CustomMembershipProvider : MembershipProvider, ICustomMembershipProvider
    {
        #region fields

        bool requiresUniqueEmail = false;
        bool requiresQuestionAndAnswer = false;
        bool enablePasswordRetrieval = false;
        bool enablePasswordReset = false;
        int maxInvalidPasswordAttempts = 0;
        int minRequiredNonAlphanumericCharacters = 0;
        int minRequiredPasswordLength = 0;
        int passwordAttemptWindow = 0;
        string name = null, description = null;
        MembershipPasswordFormat passwordFormat = MembershipPasswordFormat.Clear; 
        #endregion

        #region Properties
        public IMerazContext Context { get; set; }
        public IMemberRepository MemberRepository { get; set; }
        public IMemberRoleRepository MemberRoleRepository { get; set; }
        public override string Name
        {
            get
            {
                return name;
            }
        }
        public override string Description
        {
            get
            {
                return description;
            }
        }
        public override string ApplicationName { get; set; }

        public override bool EnablePasswordReset
        {
            get { return enablePasswordReset; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return enablePasswordRetrieval; }
        }
        public override bool RequiresQuestionAndAnswer
        {
            get { return requiresQuestionAndAnswer; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return requiresUniqueEmail; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return maxInvalidPasswordAttempts; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }


        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return minRequiredNonAlphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return minRequiredPasswordLength; }
        }

        public override int PasswordAttemptWindow
        {
            get { return passwordAttemptWindow; }
        }

        #endregion
        public CustomMembershipProvider()
        {
            if (HttpContext.Current != null && HttpContext.Current.Items.Contains("Factory"))
            {
                IObjectFactory factory = HttpContext.Current.Items["Factory"] as IObjectFactory;
                MemberRepository = factory.GetMemberRepository();
                MemberRoleRepository = factory.GetMemberRoleRepository();
            }
            else
            {
                MemberRepository = new MemberRepository();
                MemberRoleRepository = new MemberRoleRepository();
            }
            Context = MerazContext.Instance;
        }

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                throw new ArgumentNullException("name");
            this.name = name;

            if (!String.IsNullOrEmpty(config["applicationName"]))
            {
                ApplicationName = config["applicationName"];
            }

            if (!String.IsNullOrEmpty(config["requiresUniqueEmail"]))
            {
                bool result;
                if (Boolean.TryParse(config["requiresUniqueEmail"], out result))
                    requiresUniqueEmail = result;
            }
            if (!String.IsNullOrEmpty(config["requiresQuestionAndAnswer"]))
            {
                bool result;
                if (Boolean.TryParse(config["requiresQuestionAndAnswer"], out result))
                    requiresQuestionAndAnswer = result;
            }
            if (!String.IsNullOrEmpty(config["enablePasswordRetrieval"]))
            {
                bool result;
                if (Boolean.TryParse(config["enablePasswordRetrieval"], out result))
                    enablePasswordRetrieval = result;
            }
            if (!String.IsNullOrEmpty(config["enablePasswordReset"]))
            {
                bool result;
                if (Boolean.TryParse(config["enablePasswordReset"], out result))
                    enablePasswordReset = result;
            }
            if (!String.IsNullOrEmpty(config["maxInvalidPasswordAttempts"]))
            {
                int result;
                if (int.TryParse(config["maxInvalidPasswordAttempts"], out result))
                    maxInvalidPasswordAttempts = result;
            }
            if (!String.IsNullOrEmpty(config["minRequiredNonAlphanumericCharacters"]))
            {
                int result;
                if (int.TryParse(config["minRequiredNonAlphanumericCharacters"], out result))
                    minRequiredNonAlphanumericCharacters = result;
            }
            if (!String.IsNullOrEmpty(config["minRequiredPasswordLength"]))
            {
                int result;
                if (int.TryParse(config["minRequiredPasswordLength"], out result))
                    minRequiredPasswordLength = result;
            }
            if (!String.IsNullOrEmpty(config["passwordAttemptWindow"]))
            {
                int result;
                if (int.TryParse(config["passwordAttemptWindow"], out result))
                    passwordAttemptWindow = result;
            }
            if (!String.IsNullOrEmpty(config["passwordFormat"]))
            {
                switch (config["passwordFormat"].ToLower())
                {
                    case "clear": passwordFormat = MembershipPasswordFormat.Clear; break;
                    case "encrypted": passwordFormat = MembershipPasswordFormat.Encrypted; break;
                    case "hashed": passwordFormat = MembershipPasswordFormat.Hashed; break;
                    default: passwordFormat = MembershipPasswordFormat.Clear; break;
                }
            }

        }

        public void Commit()
        {
            Context.Save();
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            if (username == null || oldPassword == null || newPassword == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(oldPassword) || string.IsNullOrWhiteSpace(newPassword))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (member == null)
                return false;

            if (member.Password != Member.CreatePasswordHash(oldPassword, member.Salt))
                return false;

            member.Password = Member.CreatePasswordHash(newPassword, member.Salt);
            Commit();
            return true;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            if (username == null || password == null || newPasswordQuestion == null || newPasswordAnswer == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(newPasswordQuestion)|| string.IsNullOrWhiteSpace(newPasswordAnswer))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (member == null)
                return false;

            if (member.Password != Member.CreatePasswordHash(password, member.Salt))
                return false;
            member.PasswordQuestion = newPasswordQuestion;
            member.PasswordAnswer = newPasswordAnswer;
            Commit();
            return true;
        }

        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            
            status = MembershipCreateStatus.UserRejected;
            if (username == null || password == null || email == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(email))
                throw new ArgumentException();
                
            status = MembershipCreateStatus.Success;
            List<Member> members = MemberRepository.GetByApplicationName(ApplicationName);
            if (members.Contains(new Member() { UserName = username, ApplicationName = ApplicationName }))
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }
            string pKey;
            if (providerUserKey != null && providerUserKey is Guid)
                pKey = providerUserKey.ToString();
            else
                pKey = Guid.NewGuid().ToString();
            if (members.Contains(new Member() { ProviderUserKey = pKey }, new MemberKeyComparer()))
            {
                status = MembershipCreateStatus.DuplicateProviderUserKey;
                return null;
            }
            if (string.IsNullOrWhiteSpace(email))//(!member.IsEmailValid())
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }
            if (members.Contains(new Member() { Email = email}, new MemberEmailComparer()))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }
            if (string.IsNullOrWhiteSpace(password))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            Member member = new Member();
            MemberRepository.Add(member);
            member.ApplicationName = ApplicationName;
            member.UserName = username;
            member.ProviderName = name;
            member.Email = email;
            member.ProviderUserKey = pKey;
            member.PasswordQuestion = passwordQuestion;
            member.PasswordAnswer = passwordAnswer;
            member.Salt = Member.CreateSalt(10);
            member.Password = Member.CreatePasswordHash(password, member.Salt);
            member.IsApproved = isApproved;

            Commit();
            return member.User;
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentException();

            List<MemberRole> memberRoles = MemberRoleRepository.Get(ApplicationName, username);
            for (int i = 0; i < memberRoles.Count; i++)
            {
                MemberRoleRepository.Delete(memberRoles[i]);
            }
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (member == null)
                return false;

            MemberRepository.Delete(member);
            Commit();
            return true;
        }


        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            MembershipUserCollection collection = new MembershipUserCollection();
            List<Member> all = MemberRepository.GetByApplicationName(ApplicationName);
            totalRecords = all.Count;
            IEnumerable<Member> page = all.Skip(pageSize * pageIndex).Take(pageSize);
            foreach (Member member in page)
                collection.Add(member.User);
            return collection;
        }

        public override int GetNumberOfUsersOnline()
        {
            return MemberRepository.GetByApplicationName(ApplicationName).Where(where1 => where1.IsOnline == true).Count();
        }

        public override string GetPassword(string username, string answer)
        {
            if (username == null || answer == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(answer) )
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            return member.Password;
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            if (username == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (member == null)
                return null;
            if (userIsOnline)
                member.LastActivityDate = DateTime.Now;
            Commit();
            return member.User;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            if (providerUserKey == null)
                throw new ArgumentNullException();

            Member member = MemberRepository.GetByUserName(ApplicationName, providerUserKey.ToString());
            if (member == null)
                return null;
            if (userIsOnline)
                member.LastActivityDate = DateTime.Now;
            Commit();
            return member.User;
        }

        public override string GetUserNameByEmail(string email)
        {
            if (email == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(email))
                throw new ArgumentException();

            Member member = MemberRepository.GetByEmail(ApplicationName, email);
            if (member == null)
                return string.Empty;
            return member.UserName;
        }
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        public override string ResetPassword(string username, string answer)
        {
            if (!EnablePasswordReset)
                throw new NotSupportedException();

            if (username == null || answer == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(answer))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (RequiresQuestionAndAnswer)
            {
                if (member.PasswordAnswer != answer)
                    throw new MembershipPasswordException();
            }
            member.Password = Member.CreatePasswordHash("changeme", member.Salt);
            Commit();
            return "changeme";
        }

        public override bool UnlockUser(string userName)
        {
            if (userName == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(userName))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, userName);
            if (member == null)
            {
                return false;
            }
            member.IsLockedOut = false;
            Commit();
            return true;
        }

        public override void UpdateUser(MembershipUser user)
        {
            Member member = MemberRepository.GetByUserName(ApplicationName, user.UserName);
            if (member == null)
                return;
            member.Comment = user.Comment;
            member.CreationDate = user.CreationDate;
            member.Email = user.Email;
            member.IsApproved = user.IsApproved;
            member.IsLockedOut = user.IsLockedOut;
            member.IsOnline = user.IsOnline;
            member.LastActivityDate = user.LastActivityDate;
            member.LastLockoutDate = user.LastLockoutDate;
            member.LastPasswordChangedDate = user.LastPasswordChangedDate;
            member.LastLoginDate = user.LastLoginDate;
            member.PasswordQuestion = user.PasswordQuestion;
            Commit();
        }

        public override bool ValidateUser(string username, string password)
        {
            if (username == null || password == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
                throw new ArgumentException();
            Member member = MemberRepository.GetByUserName(ApplicationName, username);
            if (member == null)
            {
                return false;
            }
            return member.Password == Member.CreatePasswordHash(password, member.Salt);
        }
    }
}
